跳到主要内容

Go 的 ResponseWriter 接口解析

ResponseWriter 接口

在 Go 语言中,客户端请求信息都封装到了 Request 对象,但是发送给客户端的响应并不是 Response 对象,而是 ResponseWriter:

func Home(w http.ResponseWriter, r *http.Request)  {
io.WriteString(w, "Welcome to my blog site")
}

ResponseWriter 是处理器用来创建 HTTP 响应的接口,其源码结构如下所示:

type ResponseWriter interface {
// 用于设置/获取所有响应头信息
Header() Header
// 用于写入数据到响应实体
Write([]byte) (int, error)
// 用于设置响应状态码
WriteHeader(statusCode int)
}

实际上,在底层支撑 ResponseWriter 的结构体就是 http.response,详见 net/http 包下 server.go 中的 readRequest 方法(调用处理器处理 HTTP 请求时调用了该方法返回响应对象)

并且其返回值是 response 指针,这也是为什么在处理器方法声明的时候 Request 是指针类型,而 ResponseWriter 不是,实际上在底层,响应对象也是指针类型,因为在应用代码中需要设置响应头和响应实体,所以响应对象理应是指针类型

func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
...

w = &response{
conn: c,
cancelCtx: cancelCtx,
req: req,
reqBody: req.Body,
handlerHeader: make(Header),
contentLength: -1,
closeNotifyCh: make(chan bool, 1),
wants10KeepAlive: req.wantsHttp10KeepAlive(),
wantsClose: req.wantsClose(),
}
if isH2Upgrade {
w.closeAfterReply = true
}
w.cw.res = w
w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize)
return w, nil
}

response 结构体定义和 ResponseWriter 一样都位于 server.go,不过由于 response 对外不可见,所以只能通过 ResponseWriter 接口访问它。两者之间的关系是 ResponseWriter 是一个接口,而 http.response 实现了它。当我们引用 ResponseWriter 时,实际上引用的是 http.response 对象实例。

设置响应状态码

如上面的 ResponseWriter 接口定义源码所示,它包含三个方法:

  • WriteHeader
  • Header
  • Write

WriteHeader 这个方法名有点误导,其实它并不是用来设置响应头的,该方法支持传入一个整型数据用来表示响应状态码,如果不调用该方法的话,默认响应状态码是 200 OK。

WriteHeader 的主要作用是在 API 接口中返回错误码,例如,可以新增一个处理器方法 Error,并通过 w.WriteHeader 返回一个 401 未认证状态码(注意在运行时 w 代表的是对应的 response 对象实例,而不是接口):

func Error(w http.ResponseWriter, r *http.Request)  {
w.WriteHeader(401)
fmt.Fprintln(w, "认证后才能访问该接口")
}

注:这里通过 fmt.Fprintln 将文本字符串写入响应对象。

设置响应头(重定向)

Header 方法用于设置响应头信息,我们可以通过 w.Header().Set 方法设置响应头(w.Header() 方法返回的是 Header 响应头对象,它和请求头共用一个结构体,因此请求头上支持的方法这里都支持,比如可以通过 w.Header().Add 方法新增响应头),这里我们设置一个 301 重定向响应,只需要通过 w.WriteHeader 方法将响应状态码设置为 301,再通过 w.Header().Set 方法将负责重定向的响应头 Location 设置为一个可访问域名即可。

编写重定向实现代码如下:

func Redirect(w http.ResponseWriter, r *http.Request)  {
// 设置一个 301 重定向
w.Header().Set("Location", "https://example.com")
w.WriteHeader(301)
}

对于重定向请求,无需设置响应实体,另外需要注意的是 w.Header().Set 必须在 w.WriteHeader 之前调用,因为一旦调用 w.WriteHeader 之后,就不能对响应头进行设置了。

写入数据到响应实体

Write 方法用于写入数据到 HTTP 响应实体,如果调用 Write 方法时还不知道 Content-Type,会通过数据的前 512 个字节进行判断。

可以通过 w.Write 写入一段欢迎文本到响应实体:

func Home(w http.ResponseWriter, r *http.Request)  {
w.Write([]byte("hello world"));
}

由于 Write 方法接受的参数类型是 []byte 切片,所以需要将字符串转换为字节切片类型。启动 HTTP 服务器,通过 curl 访问首页,就可以看到返回的文本信息了:

返回 JSON 格式数据

可以返回 JSON 格式数据:

type Greeting struct {
Message string `json:"message"`
}

func Home(w http.ResponseWriter, r *http.Request) {
// 返回 JSON 格式数据
greeting := Greeting{
"hello world",
}
message, _ := json.Marshal(greeting)
w.Header().Set("Content-Type", "application/json")
w.Write(message)
}